﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Corecalc;
using System.Diagnostics;

namespace Sisal
{
    public enum NodeState { NotReferenced, Referenced }

    public abstract class GRNode 
    {
        public static Dictionary<FullCellAddr, GRNode> nodeEnvironment = new Dictionary<FullCellAddr, GRNode>();

        public GRNode ComplexAncestor { get; set; }        //Compound or Parallel ancestor
        public GRNode FormulaNode { get; set; }       
        public GRNode Parent { get; set; }
        public Task Task { get; set; }
        public Task TempTask { get; set; }
        public int Depth { get; set; }

        public bool DFSMarked { get; set; }
        public List<GRNode> InputEdges { get; set; }   
        public List<GRNode> OutputEdges { get; set; }
        public List<GRNode> Nodes { get; set; }          //Only non-null, if formula node

        public double Overhead { get; set; }
        public double CP { get; set; }

        public virtual double ExecutionTimeCost { get; set; }

        public int T_in { get; set; }
        public virtual int T_out { get; set; }

        public int F { get { return 1; } }      //Only set if IsFormulaNode is true
        public FullCellAddr Fca { get; set; }
        public bool IsFormulaNode { get; set; }
        public virtual NodeState State { get; set; }

        public int InpComCost {
            get { return InputEdges.Sum(e => e.OutComCost); }
        }

        public int OutComCost {
            get { return 0; }
        }

        public GRNode() {
            InputEdges = new List<GRNode>();
            OutputEdges = new List<GRNode>();

            T_in = InputEdges.Sum(x => x.T_out);
        }

        //Returns the node in the graph represented by this node 
        //(should only be called on be the global root node)
        //Does not include sub graphs of compound- and parallel nodes

        public virtual List<GRNode> GetGraph(bool subGraph) {
            List<GRNode> ns = new List<GRNode>();

            if (!ns.Contains(this)) {
                ns.Add(this);
            }

            List<GRNode> edges = subGraph ? InputEdges : OutputEdges;
            foreach (GRNode node in edges) {
                List<GRNode> nss = node.GetGraph(subGraph);
                foreach (GRNode node2 in nss) {
                    if (!ns.Contains(node2)) {
                        ns.Add(node2);
                    }
                }
            }

            foreach (GRNode node in InputEdges) {
                if (node is ConstNode) {
                    ns.Add(node);
                }
            }

            return ns;
        }

        //Returns all nodes in the graph with this node as its root
        //Does NOT include subgraphs of compound and parallel nodes
        public virtual List<GRNode> GetLocalGraph() {
            if (IsFormulaNode && Nodes != null) {
                return Nodes;
            } else if (IsFormulaNode && Nodes == null) {
                Nodes = GetLocalGraphNodes();
                return Nodes;
            } else {
                return GetLocalGraphNodes();
            }
        }

        private List<GRNode> GetLocalGraphNodes() {
            List<GRNode> ns = new List<GRNode>();
            foreach (GRNode inp in InputEdges) {
                if (!inp.IsFormulaNode && inp.FormulaNode == this.FormulaNode) {
                    ns.AddRange(inp.GetLocalGraphNodes());
                }
            }

            ns.Add(this);
            return ns;
        }

        public virtual void SetParent(GRNode parent, GRNode complexAncestor, bool @continue, List<FullCellAddr> path) {
            var a = this;
            Parent = parent;
            ComplexAncestor = complexAncestor;

            //If the sender of a value (inputedge) is not on the path from the root to this node (via output edges), 
            //then we need to set its parent recursively using the input edges of this node
            foreach (GRNode inputEdge in InputEdges) {
                if (!path.Contains(inputEdge.Fca) || Fca == inputEdge.Fca) {
                    inputEdge.SetParent(parent, complexAncestor, false, path);
                }
            }

            if (@continue) {
                foreach (GRNode node in OutputEdges)
                    node.SetParent(parent, complexAncestor, true, path);
            }
        }

        public void SetFormulaNode(GRNode formulaNode) {
            FormulaNode = formulaNode;

            foreach (GRNode node in InputEdges)
                node.SetFormulaNode((node.IsFormulaNode ? node : formulaNode));
        }

        //public bool DFS(Action<GRNode> f) {
        //    f(this);
        //    foreach (GRNode node in OutputEdges) {
        //        f(node);
        //    }
        //    foreach (GRNode node in OutputEdges) {
        //        node.DFS(f);
        //    }
        //    return false;
        //}

        public void DFS(Action<GRNode> f) {
            HashSet<GRNode> nodes = new HashSet<GRNode>();

            Stack<GRNode> stack = new Stack<GRNode>();
            stack.Push(this);
            while (stack.Count() > 0) {
                GRNode node = stack.Pop();
                if (!node.DFSMarked) {
                    f(node);
                    node.DFSMarked = true;
                    nodes.Add(node);
                    foreach (GRNode outputEdge in node.OutputEdges) {
                        stack.Push(outputEdge);
                    }
                }
            }

            foreach (GRNode node in nodes) {
                node.DFSMarked = false;
            }
        }

        public virtual bool IsConstNode { get { return false; } }

        public override string ToString() {
            return string.Format("[Loc: {0}, ParentType: {1}, Formula: {2}, Ft: {3}, Tin: {4}, Tout: {5}]: \t", 
                Fca.ca,
                Parent == this ? this.GetType().Name : Parent.GetType().Name,
                IsFormulaNode, 
                ExecutionTimeCost.ToString().Replace(",", "."), 
                T_in, 
                T_out);
        }
    }

    class SimpleNode : GRNode
    {

    }

    class CompoundNode : GRNode
    {
        public List<GRNode> Graphs { get; set; }

        public CompoundNode() {
            Graphs = new List<GRNode>();
        }

        //NB: Overridden in class IfNode (takes MAX of true / false)
        public virtual double ComputeExecutionTimeCost() {
            return Graphs.Sum(x => x.ExecutionTimeCost);
        }
    }

    // Represents genuine calls to built-in functions. 
    // Calls like "SUM(A1:B2)", "AVG(A1:B)" etc.
    class BuiltInFunctionCallNode : GRNode
    {
        public string Name { get; set; }
        public GRNode[] Args { get; set; }
        public int LastInputSize { get; set; }
        public bool Par { get; set; }

        public BuiltInFunctionCallNode(string name, GRNode[] args) {
            Name = name;
            Args = args;
            LastInputSize = 0;

            foreach (GRNode arg in args) {
                InputEdges.Add(arg);
                arg.OutputEdges.Add(this);
            }

            Debug.Assert(name.Equals("SUM") || name.Equals("COS") || name.Equals("SIN") || name.Equals("SQRT"));

            T_in = args.Sum(arg => arg.T_out);
            T_out = MultiProcessorModel.GetCommunicationCost("double");   //Should depend on the concrete function that is called
        }

        public override string ToString() {
            return base.ToString() + string.Format("BuiltInFunctionCallNode [name: {0}]", Name);
        }
    }

    class ParallelNode : GRNode
    {
        public double F { get; set; }
        public int Size { get; set; }
        public GRNode Graph { get; set; }

        public ParallelNode(GRNode g) {
            Graph = g;
            F = 1.0;
        }
    }

    class CellRefNode : SimpleNode
    {
        public CellRefNode(GRNode referredNode) {
            InputEdges.Add(referredNode);
            referredNode.OutputEdges.Add(this);

            T_in = T_out = referredNode.T_out;
        }

        public override string ToString() {
            return base.ToString() + string.Format("CellRefNode [InputEdges: {0}]", InputEdges.Count());
        }
    }
    
    class CellAreaNode : SimpleNode
    {
        public void ComputeCommunicationCosts() {
            T_out = T_in = InputEdges.Sum(ie => ie.T_out);
        }

        public override string ToString() {
            return base.ToString() + string.Format("CellAreaNode [InputEdges: {0}]", InputEdges.Count());
        }
    }

    class BinOperator : SimpleNode
    {
        public string Operator  { get; set; }
        public GRNode Left { get; set; }
        public GRNode Right { get; set; }

        public BinOperator(string binOp, GRNode left, GRNode right) {
            Operator = binOp;
            Left = left;
            Right = right;

            this.InputEdges.Add(left);
            this.InputEdges.Add(right);

            left.OutputEdges.Add(this);
            right.OutputEdges.Add(this);

            T_in = left.T_out + right.T_out;
            T_out = MultiProcessorModel.GetCommunicationCost("double");
            ExecutionTimeCost = MultiProcessorModel.GetExecutionTime(binOp);
        }

        public override string ToString() {
            return base.ToString() + string.Format("BinOperator [{0}]", Operator);
        }
    }

    class IfNode : CompoundNode
    {
        public IfNode(GRNode cond, GRNode trueBranch, GRNode falseBranch) {
            InputEdges.Add(cond);
            cond.OutputEdges.Add(this);

            Graphs.Add(trueBranch);
            Graphs.Add(falseBranch);

            T_in = cond.T_out;
            T_out = Math.Max(trueBranch.T_out, falseBranch.T_out);
        }

        public override double ComputeExecutionTimeCost() {
            double @true = Graphs[0].ExecutionTimeCost;
            double @false = Graphs[1].ExecutionTimeCost;

            return Math.Max(@true, @false);
        }

        public override void SetParent(GRNode parent, GRNode complexAncestor, bool @continue, List<FullCellAddr> path) {
            Parent = parent;
            ComplexAncestor = complexAncestor;

            foreach (GRNode graph in Graphs) {
                graph.SetParent(graph, this, @continue, path);
            }
        }

        public override string ToString() {
            return base.ToString() + string.Format("IfNode: [InputEdges: {0}, OutputEdges: {1}]", InputEdges.Count, OutputEdges.Count);
        }
    }

    class ConstNode : SimpleNode
    {
        public Const Const { get; set; }
        public bool IsUptodateNode { get; set; }

        public ConstNode(Const value, FullCellAddr fca) {
            Const = value;

            Fca = fca;
            T_in = 0;
            T_out = Const.CommunicationCost;
            ExecutionTimeCost = MultiProcessorModel.GetExecutionTime("Const");
            IsUptodateNode = false;
        }

        public ConstNode(FullCellAddr referredFca) {
            Fca = referredFca;
            T_in = 0;
            T_out = MultiProcessorModel.GetCommunicationCost(referredFca);
            ExecutionTimeCost = MultiProcessorModel.GetExecutionTime("Const");
            IsUptodateNode = true;
        }

        public override string ToString() {
            return base.ToString() + string.Format("ConstNode [value: {0}]", Const.ToString());
        }

        public override bool IsConstNode {
            get { return true; }
        }
    }
}
